#include "GraphicsEngine.hpp"

//--------------------- Camera ---------------------
void Camera::setPosition(vec3 _position)
{
    position = _position;
}

vec3 Camera::getView()
{
    return view;
}

vec3 Camera::getPosition()
{
    return position;
}

void Camera::cameraBH(){}

float Camera::getHeight()
{
    return 0;
}

vec2 Camera::getMapCords()
{
    vec2 cord;
    cord.x = degrees(position.x)/R;
    cord.y = degrees(2*atan(exp(position.y/R)) - M_PI/2);
    return cord;
}

void Camera::updateLastScroll()
{
    lastScroll = glfwGetMouseWheel();
}

//--------------------- Lock Camera ---------------------
LockCamera::LockCamera(mat4 *_View, float *_FPS)
{
    View = _View;
    FPS = _FPS;

    position.x = 14;
    position.y = 100*log(tan(radians(45.0 + 48.0)/2.0));
    position.z = MIN_CAMERA_HEIGHT;

    view.x = 14;
    view.y = 100*log(tan(radians(45.0 + 48.0)/2.0));
    view.z = 0;

    up.x = 0;
    up.y = 1;
    up.z = 0;

    height = MAX_CAMERA_HEIGHT;

    cameraSpeedX = 0.8;
    cameraSpeedY = 0.4;

    lastScroll = glfwGetMouseWheel();
}

void LockCamera::cameraBH()
{
    float deltaTime = 1.0 / *FPS;

    int scroll = glfwGetMouseWheel();
    if(lastScroll != scroll)
    {
        height += (lastScroll - scroll) * height * 0.1;
        lastScroll = scroll;
    }

    if(glfwGetKey( GLFW_KEY_LEFT ) == GLFW_PRESS)
    {
        position.x -= height * cameraSpeedX * deltaTime;
    }

    if(glfwGetKey( GLFW_KEY_RIGHT ) == GLFW_PRESS)
    {
        position.x += height * cameraSpeedX * deltaTime;
    }

    if(glfwGetKey( GLFW_KEY_DOWN ) == GLFW_PRESS)
    {
        position.y -= height * cameraSpeedY * deltaTime;
    }

    if(glfwGetKey( GLFW_KEY_UP ) == GLFW_PRESS)
    {
        position.y += height * cameraSpeedY * deltaTime;
    }

    view.x = position.x;
    view.y = position.y;

    position.z = height;

    *View = glm::lookAt(position,
                        view,
                        up);
}

float LockCamera::getHeight()
{
    return height;
}

//--------------------- Lock Camera ---------------------
FreeCamera::FreeCamera(mat4 *_View, float *_FPS) : centerX(1024/2), centerY(768/2)
{
    View = _View;
    FPS = _FPS;

    position.x = 0;
    position.y = 0;
    position.z = 0;

    view.x = 0;
    view.y = 0;
    view.z = -1;

    up.x = 0;
    up.y = 1;
    up.z = 0;

    mouseSpeed = 1;

    cameraSpeedX = 0.8;
    cameraSpeedY = 0.4;
}

void FreeCamera::cameraBH()
{
    float deltaTime = 1.0 / *FPS;

    int xpos, ypos;
    glfwGetMousePos(&xpos, &ypos);

    if(glfwGetKey( GLFW_KEY_LEFT ) == GLFW_PRESS)
    {
        position -= height * cross(view, up) * cameraSpeedX * deltaTime;
    }

    if(glfwGetKey( GLFW_KEY_RIGHT ) == GLFW_PRESS)
    {
        position += height * cross(view, up) * cameraSpeedX * deltaTime;
    }

    if(glfwGetKey( GLFW_KEY_DOWN ) == GLFW_PRESS)
    {
        position -= height * view * cameraSpeedX * deltaTime;
    }

    if(glfwGetKey( GLFW_KEY_UP ) == GLFW_PRESS)
    {
        position += height * view * cameraSpeedX * deltaTime;
    }

    float y = mouseSpeed * deltaTime * (float)( centerX - xpos );
    float x = mouseSpeed * deltaTime * (float)( centerY - ypos );

    if(x != 0 || y != 0)
    {
        vec3 axis = cross(view, up);
        axis = normalize(axis);

        view = rotate(view, x, axis);
        view = rotateY(view, y);
    }

    *View = glm::lookAt(position,
                        position + view,
                        up);

    height = position.z;

    glfwSetMousePos(centerX, centerY);
}

float FreeCamera::getHeight()
{
    return height;
}

//--------------------- Shpere Lock Camera ---------------------
SphereLockCamera::SphereLockCamera(mat4 *_View, float *_FPS)
{
    View = _View;
    FPS = _FPS;

    height = 15000;

    position.x = 0;
    position.y = 0;
    position.z = height;

    view.x = 0;
    view.y = 0;
    view.z = 0;

    up.x = 0;
    up.y = 1;
    up.z = 0;

    mapPos.x = 48;
    mapPos.y = 15;

    cameraSpeedX = 1;
    cameraSpeedY = 1;

    lastScroll = glfwGetMouseWheel();
}

void SphereLockCamera::cameraBH()
{
    float deltaTime = 1.0 / *FPS;

    int scroll = glfwGetMouseWheel();
    if(lastScroll != scroll)
    {
        height += (lastScroll - scroll) * (height - R) * 0.1;
        lastScroll = scroll;
    }

    if(glfwGetKey( GLFW_KEY_LEFT ) == GLFW_PRESS)
    {
        mapPos.y -= cameraSpeedX * deltaTime * (height - R) * 0.01;
    }

    if(glfwGetKey( GLFW_KEY_RIGHT ) == GLFW_PRESS)
    {
        mapPos.y += cameraSpeedX * deltaTime * (height - R) * 0.01;
    }

    if(glfwGetKey( GLFW_KEY_DOWN ) == GLFW_PRESS)
    {
        mapPos.x -= cameraSpeedY * deltaTime * (height - R) * 0.01;
    }

    if(glfwGetKey( GLFW_KEY_UP ) == GLFW_PRESS)
    {
        mapPos.x += cameraSpeedY * deltaTime * (height - R) * 0.01;
    }

    position.x = height * cos(radians(mapPos.x)) * sin(radians(mapPos.y));
    position.z = height * cos(radians(mapPos.x)) * cos(radians(mapPos.y));
    position.y = height * sin(radians(mapPos.x));

    *View = glm::lookAt(position,
                        view,
                        up);
}

float SphereLockCamera::getHeight()
{
    return height - R;
}

vec2 SphereLockCamera::getMapCords(){
    vec2 cord;
    cord.y = degrees(atan2(position.y, sqrt(position.x*position.x+position.z*position.z)));
    cord.x = degrees(atan2(position.x,position.z));
    return cord;
}

//--------------------- Shpere Free Camera ---------------------
SphereFreeCamera::SphereFreeCamera(mat4 *_View, float *_FPS) : centerX(1024/2), centerY(768/2)
{
    View = _View;
    FPS = _FPS;

    position.x = 0;
    position.y = 0;
    position.z = 0;

    view.x = 0;
    view.y = 0;
    view.z = -1;

    up.x = 0;
    up.y = 1;
    up.z = 0;

    mouseSpeed = 1;

    cameraSpeedX = 0.8;
    cameraSpeedY = 0.4;
}

void SphereFreeCamera::cameraBH()
{
    float deltaTime = 1.0 / *FPS;

    int xpos, ypos;
    glfwGetMousePos(&xpos, &ypos);

    if(glfwGetKey( GLFW_KEY_LEFT ) == GLFW_PRESS)
    {
        position -= (height - R) * cross(view, up) * cameraSpeedX * deltaTime;
    }

    if(glfwGetKey( GLFW_KEY_RIGHT ) == GLFW_PRESS)
    {
        position += (height - R) * cross(view, up) * cameraSpeedX * deltaTime;
    }

    if(glfwGetKey( GLFW_KEY_DOWN ) == GLFW_PRESS)
    {
        position -= (height - R) * view * cameraSpeedX * deltaTime;
    }

    if(glfwGetKey( GLFW_KEY_UP ) == GLFW_PRESS)
    {
        position += (height - R) * view * cameraSpeedX * deltaTime;
    }

    float y = mouseSpeed * deltaTime * (float)( centerX - xpos );
    float x = mouseSpeed * deltaTime * (float)( centerY - ypos );

    if(x != 0 || y != 0)
    {
        vec3 axis = cross(view, up);
        axis = normalize(axis);

        view = rotate(view, x, axis);
        view = rotateY(view, y);
    }

    *View = glm::lookAt(position,
                        position + view,
                        up);

    height = length(position);

    glfwSetMousePos(centerX, centerY);
}

float SphereFreeCamera::getHeight()
{
    return height - R;
}

vec2 SphereFreeCamera::getMapCords(){
    vec2 cord;
    cord.y = degrees(atan2(position.y, sqrt(position.x*position.x+position.z*position.z)));
    cord.x = degrees(atan2(position.x,position.z));
    return cord;
}

//--------------------- Graphics engine ---------------------

//-------------- Private --------------

//------- FPS -------
void GraphicEngine::initCalculateFPS()
{
    frameCount = 0;
    fps = 0;
    currentTime = 0;
    previousTime = 0;

    strcpy(cfps,"FPS: ");
    strcpy(cPosiotion,"Position: ");
}

void GraphicEngine::calculateFPS()
{
    frameCount++;

    currentTime = glutGet(GLUT_ELAPSED_TIME);

    timeInterval = currentTime - previousTime;

    if(timeInterval > 100)
    {
        fps = frameCount / (timeInterval / 1000.0f);

        previousTime = currentTime;

        frameCount = 0;

        sprintf(&cfps[5],"%f",fps);
    }
}

//------- Auto LOD -------
void GraphicEngine::initAutoLOD()
{
    autoLODon = false;
    strcpy(cAutoLOD,"Auto LOD: ");

    lastCamCords.x = 0;
    lastCamCords.y = 0;

    autoLODvar = 0;
}

void GraphicEngine::autoLOD(MapsCon *maps, Camera *cam)
{
    vec2 cord = cam->getMapCords();

    if(autoLODon){
        if(timeInterval > 100)
        {
            if(fps < 10 && autoLODvar < 8) autoLODvar++;
            else if(fps > 55 && autoLODvar > 0) autoLODvar--;
        }

        if(lastCamCords != cord){
            maps->changLOD(0 + autoLODvar);

            maps->changeMapLOD(0, cord.x + 180, cord.y + 180 + 1);

            maps->changeMapLOD(1, cord.x + 180 - 1, cord.y + 180 + 1);
            maps->changeMapLOD(1, cord.x + 180 + 1, cord.y + 180 + 1);
            maps->changeMapLOD(1, cord.x + 180, cord.y + 180);
            maps->changeMapLOD(1, cord.x + 180, cord.y + 180 + 2);

            maps->changeMapLOD(1, cord.x + 180 - 1, cord.y + 180 + 2);
            maps->changeMapLOD(1, cord.x + 180 + 1, cord.y + 180 + 2);
            maps->changeMapLOD(1, cord.x + 180 - 1, cord.y + 180);
            maps->changeMapLOD(1, cord.x + 180 + 1, cord.y + 180);
        }

        sprintf(&cAutoLOD[10], "%s : %d", "ON", autoLODvar);
    } else {
        sprintf(&cAutoLOD[10], "%s", "OFF");
    }

    sprintf(&cPosiotion[10], "%.2f : %.2f", cord.x, cord.y);
}

//------- GLFW -------
int GraphicEngine::initWinodw()
{
    // Initialise GLFW
    if( !glfwInit() )
    {
        cout << "Failed to initialize GLFW\n" << endl;
        return 0;
    }

    glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
    glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3);
    glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
    glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // Open a window and create its OpenGL context
    if( !glfwOpenWindow( 1024, 768, 0,0,0,0, 32,0, GLFW_WINDOW ) )
    {
        cout << "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" << endl;
        glfwTerminate();
        return 0;
    }

    // Initialize GLEW
    glewExperimental = true;
    if (glewInit() != GLEW_OK)
    {
        cout << "Failed to initialize GLEW\n" << endl;
        return 0;
    }

    glfwSetWindowTitle( "World!" );

    // Ensure we can capture the escape key being pressed below
    glfwEnable( GLFW_STICKY_KEYS );

    glfwGetMouseWheel();


    glEnable(GL_DEPTH_TEST);
    //glDepthFunc(GL_LESS);

    // Dark blue background
    glClearColor(0.1f, 0.0f, 0.15f, 0.0f);

    glGenVertexArrays(1, &VertexArrayID);
    glBindVertexArray(VertexArrayID);

    // Initialize our little text library with the Holstein font
    initText2D( "Holstein.tga" );

    return 1;
}

//------- Camera -------
void GraphicEngine::initCams(mat4 *_View, float *_FPS)
{
    cams = new Camera*[4];

    cams[0] = new LockCamera(_View, _FPS);
    cams[1] = new FreeCamera(_View, _FPS);
    cams[2] = new SphereLockCamera(_View, _FPS);
    cams[3] = new SphereFreeCamera(_View, _FPS);

    camera = 0;
}

//------- Buttons -------
void GraphicEngine::buttons(MapsCon *maps)
{
    for(int i = 1; i < 10; i++)
    {
        if(glfwGetKey( 48 + i ) == GLFW_PRESS)
        {
            maps->changLOD(i-1);
            autoLODon = false;
        }
    }

    if(glfwGetKey( 48 ) == GLFW_PRESS && autoLoadButton)
    {
        if(autoLODon) autoLODon = false;
        else autoLODon = true;

        autoLoadButton = false;
    }else if(!autoLoadButton && !glfwGetKey( 48 ) == GLFW_PRESS){

        autoLoadButton = true;
    }
}

bool GraphicEngine::cameraChangeButton()
{
    if(glfwGetKey( 67 ) == GLFW_PRESS && CButtonState)
    {
        short oldCam = camera;

        if(camera < 1) camera++;
        else if(camera == 1) camera = 0;
        else if(camera < 3) camera++;
        else if(camera == 3) camera = 2;

        cams[camera]->setPosition(cams[oldCam]->getPosition());

        CButtonState = false;

        return true;
    }
    else if(!glfwGetKey( 67 ) == GLFW_PRESS && !CButtonState)
    {
        CButtonState = true;
    }

    return false;
}

bool GraphicEngine::viewChangeButton(MapsCon *maps, Grid *grid)
{
    if(glfwGetKey( 86 ) == GLFW_PRESS && VButtonState)
    {
        if(view < 1) view++;
        else view = 0;

        grid->setView(view);
        maps->setViews(view);

        camera = view * 2;

        VButtonState = false;

        return true;
    }
    else if(!glfwGetKey( 86 ) == GLFW_PRESS && !VButtonState)
    {
        VButtonState = true;
    }

    return false;
}

//-------------- Public --------------
GraphicEngine::GraphicEngine()
{
    initWinodw();
    initCalculateFPS();
    initAutoLOD();

    autoLoadButton = false;
    CButtonState = false;
    VButtonState = false;

    view = 0;
}

void GraphicEngine::graphicEngineLoop()
{
    // Projection matrix : 45° Field of View, 4:3 ratio, display range : 0.1 unit <-> 100 units
    Projection = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 200000.0f);

    mat4 VP = Projection * View;

    MapsCon maps((char *)"maps/");

    Grid grid;

    initCams(&View, &fps);

    Camera *cam = cams[camera];

    do
    {
        calculateFPS();

        cam->cameraBH();

        VP = Projection * View;

        //Clear the screen
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        maps.drawMaps(VP);

        grid.drawGrid(VP, cam->getHeight());

        printText2D(cfps, 10, 580, 10);
        printText2D(cAutoLOD, 10, 570, 10);
        printText2D(cPosiotion, 10, 560, 10);

        // Swap buffers
        glfwSwapBuffers();

        buttons(&maps);
        if(cameraChangeButton() || viewChangeButton(&maps, &grid))
        {
            cam = cams[camera];
            cam->updateLastScroll();
        }

        autoLOD(&maps, cam);
    }
    while( glfwGetWindowParam( GLFW_OPENED ) );
}
